package cn.com.dbridge.common.websocket.netty;

import cn.com.dbridge.common.websocket.netty.handler.MessageHandler;
import cn.com.dbridge.common.websocket.netty.handler.DeviceAuthHandler;
import cn.com.dbridge.common.websocket.netty.handler.DeviceInfoManager;
import cn.com.dbridge.common.websocket.util.Util;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.channels.spi.SelectorProvider;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
public class NettyServer {
    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);

    private NettyServer() {
    }

    private static class SingleTonHoler {
        private static NettyServer INSTANCE = new NettyServer();
    }

    public static NettyServer getInstance() {
        return SingleTonHoler.INSTANCE;
    }

    public static NettyConfig nettyConfig = null;

    private EventLoopGroup parentGroup;
    private EventLoopGroup childGroup;

    public void run(NettyConfig nettyConfig) {
        if (nettyConfig == null) {
            log.error("nettyConfig is null, run failed");
            return;
        }
        if (NettyServer.nettyConfig != null) {
            log.error("netty is already running on port:" + nettyConfig.getPort());
            return;
        }
        NettyServer.nettyConfig = nettyConfig;
        if (Util.useNettyEpoll()) {
            createEpollServer(nettyConfig);
        } else {
            createNioServer(nettyConfig);
        }
    }

    private void createEpollServer(NettyConfig nettyConfig) {
        EpollEventLoopGroup parentEpollGroup = new EpollEventLoopGroup(nettyConfig.getParentgThreadNum(),
                new DefaultThreadFactory(nettyConfig.getParentgThreadName()));
        parentEpollGroup.setIoRatio(nettyConfig.getParentgIoratio());
        this.parentGroup = parentEpollGroup;
        EpollEventLoopGroup childEpollGroup = new EpollEventLoopGroup(nettyConfig.getChildgThreadNum(),
                new DefaultThreadFactory(nettyConfig.getChildggThreadName()));
        childEpollGroup.setIoRatio(nettyConfig.getChildgIoratio());
        this.childGroup = childEpollGroup;
        createServer(parentGroup, childGroup, EpollServerSocketChannel::new);
    }

    private void createServer(EventLoopGroup parentGroup, EventLoopGroup childGroup,
                              ChannelFactory<? extends ServerChannel> channelFactory) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(parentGroup, childGroup);
        serverBootstrap.channelFactory(channelFactory);
        serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new HttpServerCodec());
                pipeline.addLast(new HttpObjectAggregator(65536));
                pipeline.addLast(new WebSocketServerCompressionHandler());
                //pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getWsPath(), null, true));
                pipeline.addLast(new DeviceAuthHandler());
                pipeline.addLast(new MessageHandler());
            }
        });

        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        serverBootstrap.childOption(ChannelOption.SO_SNDBUF, 32768);
        serverBootstrap.childOption(ChannelOption.SO_RCVBUF, 32768);

        InetSocketAddress address = new InetSocketAddress(nettyConfig.getPort());
        ChannelFuture channelFuture = serverBootstrap.bind(address).syncUninterruptibly().addListener(future -> {
            if (future.isSuccess()) {
                log.info("server start success on:{}", nettyConfig.getPort());
            } else {
                log.error("server start failure on:{}", nettyConfig.getPort(), future.cause());
            }
        });

        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                log.info("scan Not Active Channel");
                DeviceInfoManager.scanNotActiveChannel();
            }
        }, 3, 60, TimeUnit.SECONDS);

        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                DeviceInfoManager.broadCastPing();
            }
        }, 3, 50, TimeUnit.SECONDS);

        channelFuture.channel().closeFuture().addListener(future -> {
            log.info("Netty Http Server Start Shutdown ............");
            NettyServer.getInstance().stop();
        });

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                NettyServer.getInstance().stop();
            }
        });
    }

    private void createNioServer(NettyConfig nettyConfig) {
        NioEventLoopGroup parentNioGroup = new NioEventLoopGroup(nettyConfig.getParentgThreadNum(),
                new DefaultThreadFactory(nettyConfig.getParentgThreadName()), SelectorProvider.provider());
        parentNioGroup.setIoRatio(nettyConfig.getParentgIoratio());
        this.parentGroup = parentNioGroup;
        NioEventLoopGroup childNioGroup = new NioEventLoopGroup(nettyConfig.getChildgThreadNum(),
                new DefaultThreadFactory(nettyConfig.getChildggThreadName()));
        childNioGroup.setIoRatio(nettyConfig.getChildgIoratio());
        this.childGroup = childNioGroup;
        createServer(parentGroup, childGroup, NioServerSocketChannel::new);
    }


    public void stop() {
        if (this.parentGroup != null)
            this.parentGroup.shutdownGracefully().syncUninterruptibly();
        if (this.childGroup != null)
            this.childGroup.shutdownGracefully().syncUninterruptibly();
    }
}
